/**************************************************************************
 *
 * Copyright (C) 2005 Steve Karg
 *
 * SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
 *
 *********************************************************************/
#include <stdint.h>
#include "bacnet/bacdef.h"
#include "bacnet/npdu.h"
#include "bacnet/datalink/arcnet.h"
#include "bacport.h"

/** @file linux/arcnet.c  Provides Linux-specific functions for Arcnet. */

/* my local device data - MAC address */
uint8_t ARCNET_MAC_Address = 0;
/* ARCNET file handle */
static int ARCNET_Sock_FD = -1;
/* ARCNET socket address (has the interface name) */
static struct sockaddr ARCNET_Socket_Address;
/* Broadcast address */
#define ARCNET_BROADCAST 0

/*
Hints:

When using a PCI20-485D ARCNET card from Contemporary Controls,
you might need to know about the following settings:

Assuming a 20MHz clock on the COM20020 chip:

clockp Clock Prescaler DataRate
------ --------------- --------
0           8          2.5 Mbps
1           16         1.25 Mbps
2           32         625 Kbps
3           64         312.5 Kbps
4           128        156.25Kbps

1. Install the arcnet driver and arcnet raw mode driver:
# modprobe com20020_pci clockp=4
# modprobe arc_rawmode

2. Use ifconfig to bring up the interface
# ifconfig arc0 up

3. The hardware address (MAC address) is set using the dipswitch
   on the back of the card.  0 is broadcast, so don't use 0.

4. The backplane mode on the PCI20-485D card is done in hardware,
   so the driver does not need to do backplane mode.  If you
   use another type of PCI20 card, you could pass in backplane=1 or
   backplane=0 as an option to the modprobe of com20020_pci.

*/

bool arcnet_valid(void)
{
    return (ARCNET_Sock_FD >= 0);
}

void arcnet_cleanup(void)
{
    if (arcnet_valid()) {
        close(ARCNET_Sock_FD);
    }
    ARCNET_Sock_FD = -1;

    return;
}

static int arcnet_bind(const char *interface_name)
{
    int sock_fd = -1; /* return value */
    struct ifreq ifr;
    int rv; /* return value - error value from df or ioctl call */
    int uid = 0;

    /* check to see if we are being run as root */
    uid = getuid();
    if (uid != 0) {
        fprintf(
            stderr,
            "arcnet: Unable to open an af_packet socket.  "
            "Try running with root priveleges.\n");
        return sock_fd;
    }
    fprintf(stderr, "arcnet: opening \"%s\"\n", interface_name);
    /* note: on some systems you may have to add or enable in */
    /* modules.conf (or in modutils/alias on Debian with update-modules) */
    /* alias net-pf-17 af_packet */
    /* Then follow it by: # modprobe af_packet */
    if ((sock_fd = socket(PF_PACKET, SOCK_PACKET, htons(ETH_P_ALL))) < 0) {
        /* Error occured */
        fprintf(stderr, "arcnet: Error opening socket: %s\n", strerror(errno));
        fprintf(
            stderr,
            "You might need to add the following to modules.conf\n"
            "(or in /etc/modutils/alias on Debian with update-modules):\n"
            "alias net-pf-17 af_packet\n"
            "Also, add af_packet to /etc/modules.\n"
            "Then follow it by:\n"
            "# modprobe af_packet\n");
        exit(-1);
    }

    if (ARCNET_Sock_FD >= 0) {
        /* Bind the socket to an interface name so we only get packets from it
         */
        ARCNET_Socket_Address.sa_family = ARPHRD_ARCNET;
        /*ARCNET_Socket_Address.sa_family = PF_INET; */
        /* Clear the memory before copying */
        memset(
            ARCNET_Socket_Address.sa_data, '\0',
            sizeof(ARCNET_Socket_Address.sa_data));
        /* Strcpy the interface name into the address */
        snprintf(
            ARCNET_Socket_Address.sa_data,
            sizeof(ARCNET_Socket_Address.sa_data), "%s", interface_name);
        fprintf(
            stderr, "arcnet: binding \"%s\"\n", ARCNET_Socket_Address.sa_data);
        if (bind(
                sock_fd, &ARCNET_Socket_Address,
                sizeof(ARCNET_Socket_Address)) != 0) {
            /* Bind problem, close socket and return */
            fprintf(
                stderr, "arcnet: Unable to bind socket : %s\n",
                strerror(errno));
            fprintf(
                stderr,
                "You might need to add the following to modules.conf\n"
                "(or in /etc/modutils/alias on Debian with update-modules):\n"
                "alias net-pf-17 af_packet\n"
                "Also, add af_packet to /etc/modules.\n"
                "Then follow it by:\n"
                "# modprobe af_packet\n");
            /* Close the socket */
            close(sock_fd);
            exit(-1);
        }
    }
    snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", interface_name);
    rv = ioctl(sock_fd, SIOCGIFHWADDR, &ifr);
    if (rv != -1) { /* worked okay */
        ARCNET_MAC_Address = ifr.ifr_hwaddr.sa_data[0];
    }
    /* copy this info into the local copy since bind wiped it out */
    ARCNET_Socket_Address.sa_family = ARPHRD_ARCNET;
    /*ARCNET_Socket_Address.sa_family = PF_INET; */
    /* Clear the memory before copying */
    memset(
        ARCNET_Socket_Address.sa_data, '\0',
        sizeof(ARCNET_Socket_Address.sa_data));
    /* Strcpy the interface name into the address */
    snprintf(
        ARCNET_Socket_Address.sa_data, sizeof(ARCNET_Socket_Address.sa_data),
        "%s", interface_name);
    fprintf(
        stderr, "arcnet: MAC=%02Xh iface=\"%s\"\n", ARCNET_MAC_Address,
        ARCNET_Socket_Address.sa_data);

    atexit(arcnet_cleanup);

    return sock_fd;
}

bool arcnet_init(char *interface_name)
{
    if (interface_name) {
        ARCNET_Sock_FD = arcnet_bind(interface_name);
    } else {
        ARCNET_Sock_FD = arcnet_bind("arc0");
    }

    return arcnet_valid();
}

/* function to send a PDU out the socket */
/* returns number of bytes sent on success, negative on failure */
int arcnet_send_pdu(
    BACNET_ADDRESS *dest, /* destination address */
    BACNET_NPDU_DATA *npdu_data, /* network information */
    uint8_t *pdu, /* any data to be sent - may be null */
    unsigned pdu_len)
{ /* number of bytes of data */
    BACNET_ADDRESS src = { 0 }; /* source address */
    int bytes = 0;
    uint8_t mtu[512] = { 0 };
    int mtu_len = 0;
    struct archdr *pkt = (struct archdr *)mtu;

    (void)npdu_data;
    src.mac[0] = ARCNET_MAC_Address;
    src.mac_len = 1;

    /* don't waste time if the socket is not valid */
    if (ARCNET_Sock_FD < 0) {
        fprintf(stderr, "arcnet: socket is invalid!\n");
        return -1;
    }
    /* load destination MAC address */
    if (dest->mac_len == 1) {
        pkt->hard.dest = dest->mac[0];
    } else {
        fprintf(stderr, "arcnet: invalid destination MAC address!\n");
        return -2;
    }
    if (src.mac_len == 1) {
        pkt->hard.source = src.mac[0];
    } else {
        fprintf(stderr, "arcnet: invalid source MAC address!\n");
        return -3;
    }
    /* Logical PDU portion */
    pkt->soft.raw[0] = 0xCD; /* SC for BACnet */
    pkt->soft.raw[1] = 0x82; /* DSAP for BACnet */
    pkt->soft.raw[2] = 0x82; /* SSAP for BACnet */
    pkt->soft.raw[3] = 0x03; /* LLC Control byte in header */
    /* packet length */
    mtu_len = ARC_HDR_SIZE + 4 /*SC,DSAP,SSAP,LLC */ + pdu_len;
    if (mtu_len > 512) {
        fprintf(stderr, "arcnet: PDU is too big to send!\n");
        return -4;
    }
    memcpy(&pkt->soft.raw[4], pdu, pdu_len);
    /* Send the packet */
    bytes = sendto(
        ARCNET_Sock_FD, &mtu, mtu_len, 0,
        (struct sockaddr *)&ARCNET_Socket_Address,
        sizeof(ARCNET_Socket_Address));
    /* did it get sent? */
    if (bytes < 0) {
        fprintf(stderr, "arcnet: Error sending packet: %s\n", strerror(errno));
    }

    return bytes;
}

/* receives an framed packet */
/* returns the number of octets in the PDU, or zero on failure */
uint16_t arcnet_receive(
    BACNET_ADDRESS *src, /* source address */
    uint8_t *pdu, /* PDU data */
    uint16_t max_pdu, /* amount of space available in the PDU  */
    unsigned timeout)
{ /* milliseconds to wait for a packet */
    int received_bytes;
    uint8_t buf[512] = { 0 }; /* data */
    uint16_t pdu_len = 0; /* return value */
    fd_set read_fds;
    int max;
    struct timeval select_timeout;
    struct archdr *pkt = (struct archdr *)buf;

    /* Make sure the socket is open */
    if (ARCNET_Sock_FD <= 0) {
        return 0;
    }

    /* we could just use a non-blocking socket, but that consumes all
       the CPU time.  We can use a timeout; it is only supported as
       a select. */
    if (timeout >= 1000) {
        select_timeout.tv_sec = timeout / 1000;
        select_timeout.tv_usec =
            1000 * (timeout - select_timeout.tv_sec * 1000);
    } else {
        select_timeout.tv_sec = 0;
        select_timeout.tv_usec = 1000 * timeout;
    }
    FD_ZERO(&read_fds);
    FD_SET(ARCNET_Sock_FD, &read_fds);
    max = ARCNET_Sock_FD;

    if (select(max + 1, &read_fds, NULL, NULL, &select_timeout) > 0) {
        received_bytes = read(ARCNET_Sock_FD, &buf[0], sizeof(buf));
    } else {
        return 0;
    }

    /* See if there is a problem */
    if (received_bytes < 0) {
        /* EAGAIN Non-blocking I/O has been selected  */
        /* using O_NONBLOCK and no data */
        /* was immediately available for reading. */
        if (errno != EAGAIN) {
            fprintf(
                stderr, "ethernet: Read error in receiving packet: %s\n",
                strerror(errno));
        }
        return 0;
    }

    if (received_bytes == 0) {
        return 0;
    }

    /* printf("arcnet: received %u bytes (offset=%02Xh %02Xh) "
       "from %02Xh (proto==%02Xh)\n",
       received_bytes, pkt->offset[0], pkt->offset[1],
       pkt->hard.source, pkt->soft.raw[0]);
     */

    if (pkt->hard.source == ARCNET_MAC_Address) {
        fprintf(stderr, "arcnet: self sent packet?\n");
        return 0;
    }
    if (pkt->soft.raw[0] != 0xCD) {
        /* fprintf(stderr,"arcnet: Non-BACnet packet.\n"); */
        return 0;
    }
    if ((pkt->hard.dest != ARCNET_MAC_Address) &&
        (pkt->hard.dest != ARCNET_BROADCAST)) {
        fprintf(stderr, "arcnet: This packet is not for us.\n");
        return 0;
    }
    if ((pkt->soft.raw[1] != 0x82) || /* DSAP */
        (pkt->soft.raw[2] != 0x82) || /* LSAP */
        (pkt->soft.raw[3] != 0x03)) { /* LLC Control */
        fprintf(stderr, "arcnet: BACnet packet has invalid LLC.\n");
        return 0;
    }
    /* It must be addressed to us or be a Broadcast */
    if ((pkt->hard.dest != ARCNET_MAC_Address) &&
        (pkt->hard.dest != ARCNET_BROADCAST)) {
        fprintf(stderr, "arcnet: This packet is not for us.\n");
        return 0;
    }
    /* copy the source address */
    src->mac_len = 1;
    src->mac[0] = pkt->hard.source;
    /* compute the PDU length */
    pdu_len = received_bytes - ARC_HDR_SIZE;
    pdu_len -= 4 /* SC, DSAP, SSAP, LLC Control */;
    /* copy the buffer into the PDU */
    if (pdu_len < max_pdu) {
        memmove(&pdu[0], &pkt->soft.raw[4], pdu_len);
    }
    /* silently ignore packets that are too large */
    else {
        pdu_len = 0;
    }

    return pdu_len;
}

void arcnet_get_my_address(BACNET_ADDRESS *my_address)
{
    int i = 0;

    my_address->mac_len = 1;
    my_address->mac[0] = ARCNET_MAC_Address;
    my_address->net = 0; /* DNET=0 is local only, no routing */
    my_address->len = 0;
    for (i = 0; i < MAX_MAC_LEN; i++) {
        my_address->adr[i] = 0;
    }

    return;
}

void arcnet_get_broadcast_address(BACNET_ADDRESS *dest)
{ /* destination address */
    int i = 0; /* counter */

    if (dest) {
        dest->mac[0] = ARCNET_BROADCAST;
        dest->mac_len = 1;
        dest->net = BACNET_BROADCAST_NETWORK;
        dest->len = 0; /* always zero when DNET is broadcast */
        for (i = 0; i < MAX_MAC_LEN; i++) {
            dest->adr[i] = 0;
        }
    }

    return;
}
